package com.example.common.config.serializer;

import com.example.common.annotation.Sensitive;
import com.example.common.core.domain.BaseEntity;
import com.example.common.core.domain.model.LoginUser;
import com.example.common.enums.DesensitizedType;
import com.example.common.utils.SecurityUtils;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;

import javax.xml.validation.Validator;
import java.io.IOException;
import java.util.Objects;

/**
 * 敏感数据处理
 * 继承JsonSerializer，这是Jackson提供的一个抽象类，
 * 抽象类中定义了抽象方法serialize，这个方法就是将数据序列化的方法，
 * 继承抽象类后，必须实现抽象方法serialize
 * 实现了ContextualSerializer接口，这个接口中定义了一个方法createContextual，
 * 这个方法中可以获取到当前字段的属性信息，然后根据属性信息进行判断，
 * 然后根据属性信息进行判断，如果属性信息中包含敏感信息，则进行数据处理，
 * 否则直接返回原数据
 */
public class SensitiveJsonSerializer extends JsonSerializer<String> implements ContextualSerializer {

    private DesensitizedType desensitizedType;

    /**
     * 脱敏处理
     *
     * @param value
     * @param gen
     * @param serializerProvider
     * @throws IOException
     */
    @Override
    public void serialize(String value, JsonGenerator gen, SerializerProvider serializerProvider) throws IOException {
        //调用desensitization()方法判断是否需要脱敏
        if (desensitization()) {
            // 如果需要脱敏，则使用之前设置的desensitizedType对应的脱敏函数处理值
            //apply这是一个函数式接口的方法，统一接口，实现多个具体方法
            gen.writeString(desensitizedType.getDesensitizer().apply(value));
        } else {
            //否则直接输出原始值

            //gen.writeString(value) 的作用
            //这行代码是使用 Jackson 库的 JsonGenerator 对象将字符串值写入到JSON 输出流中，而不是控制台。
            //详细说明
            //JsonGenerator 的作用：
            //JsonGenerator 是 Jackson 库提供的用于生成 JSON 数据的类
            //它负责将 Java 对象序列化为 JSON 格式的字符串
            //输出目标通常是 HTTP 响应、文件或其他数据流，而不是控制台
            //writeString 方法的作用：
            //将给定的字符串值作为 JSON 字符串写入输出流
            //自动处理 JSON 转义和格式化
            //例如：如果 value 是 "张三"，它会写入 "张三"（带引号)
            gen.writeString(value);
            //让我来解释一下 gen（JsonGenerator）对象的生命周期和工作原理。
            //JsonGenerator 的生命周期
            //gen 对象不是单例，也不是静态的。它是由 Jackson 框架在序列化过程中创建和管理的临时对象。
            //工作流程
            //创建过程：
            //当 Jackson 开始序列化一个对象时，会创建一个 JsonGenerator 实例
            //这个实例关联到特定的输出目标（如 HTTP 响应流、文件流等）
            //传递过程：
            //JsonGenerator 实例作为参数传递给每个自定义序列化器的 serialize 方法
            //在我们的 SensitiveJsonSerializer.serialize() 方法中，gen 是一个有效的对象引用
            //使用过程：
            //我们调用 gen.writeString(value) 将字符串写入到 JsonGenerator 维护的输出流中
            //JsonGenerator 会将数据累积到内部缓冲区或直接写入到输出流
            //销毁过程：
            //整个对象序列化完成后，Jackson 会关闭 JsonGenerator
            //关闭时会刷新缓冲区并将所有累积的数据写入最终目标
            //然后 JsonGenerator 实例会被垃圾回收
            //为什么不会丢失
            //作用域内有效：gen 作为方法参数，在 serialize 方法执行期间始终有效
            //框架管理：Jackson 框架负责 JsonGenerator 的生命周期管理
            //同步调用：整个序列化过程是同步的，不会出现异步导致的对象失效问题
            //所以，gen 对象在 serialize 方法执行期间是完全有效的，不会丢失。
            // 这是 Jackson 框架设计的一部分，确保了序列化过程的正确性和数据完整性。
        }
    }

    /**
     * 这是一个抽象方法，这个方法中可以获取到当前字段的属性信息，然后根据属性信息进行判断，
     * 如果属性信息中包含敏感信息，则进行数据处理，否则直接返回原数据
     *
     * @param prov
     * @param property
     * @return
     * @throws JsonMappingException
     */
    @Override
    public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {
//        检查当前正在序列化的字段是否有Sensitive注解
        Sensitive annotation = property.getAnnotation(Sensitive.class);
        //如果有且字段类型为String，则获取注解中指定的脱敏类型并保存到desensitizedType字段
        if (Objects.nonNull(annotation) && Objects.equals(String.class, property.getType().getRawClass())) {
            this.desensitizedType = annotation.desensitizedType();
            return this;
        }
        //返回当前序列化器实例
        return prov.findValueSerializer(property.getType(), property);
    }

    /**
     * 判断字段是否需要脱敏
     *
     * @return
     */
    private boolean desensitization() {
        try {
            LoginUser securityUser = SecurityUtils.getLoginUser();
            //管理员不脱敏
            return !securityUser.getUser().isAdmin();
        } catch (Exception e) {
            return true;
        }
    }
}
